package org.web.locks.databasedistributedlocks.server.biz.impl;


import org.springframework.stereotype.Service;
import org.web.locks.databasedistributedlocks.server.dao.DistributedLocksSpecialDAO;
import org.web.locks.databasedistributedlocks.server.domain.DistributedReleaseLocksDO;
import org.web.locks.databasedistributedlocks.server.domain.DistributedLocksDO;
import org.web.locks.databasedistributedlocks.server.service.DistributedLocksService;

import javax.annotation.Resource;
import javax.validation.constraints.NotEmpty;

@Service("distributedLocksServiceBiz")
public class DistributedLocksServiceBizImpl {

    @Resource
    private DistributedLocksService distributedLocksService;

    @Resource
    private DistributedLocksSpecialDAO distributedLocksSpecialDAO;


    public boolean tryLock(String lockMarker, Integer lockExpireTime, String lockOwner) {
        DistributedLocksDO distributedLocksDO = new DistributedLocksDO();
        distributedLocksDO.setDistributedLocksId(lockMarker);
        distributedLocksDO.setLockExpiryTime(lockExpireTime);
        distributedLocksDO.setLockOwner(lockOwner);

        DistributedLocksDO distributedLocksDOByQuery = distributedLocksService.selectDistributedLocksByDistributedLocksId(lockMarker);
        if (distributedLocksDOByQuery != null) {
            if (distributedLocksDOByQuery.getLockOwner().equals(lockOwner)) {
                // 锁已经被当前线程获取，做可重入处理，同时更新锁获取锁的时间，进行续期, 如果续期成功，则代表再次获取锁成功。
                return tryReentrantLock(lockMarker, lockOwner, lockExpireTime);
            } else if ((distributedLocksDOByQuery.getUpdateAt().getTime() + distributedLocksDOByQuery.getLockExpiryTime()) < System.currentTimeMillis()) {
                if (tryReleaseForExpired(lockMarker)) {
                    return distributedLocksService.insertDistributedLocks(distributedLocksDO) == 1;
                }
            }
            return false;
        } else {
            return distributedLocksService.insertDistributedLocks(distributedLocksDO) == 1;
        }
    }

    public boolean releaseLock(String lockMarker, String lockOwner) {
        DistributedLocksDO distributedLocksDOByQuery = distributedLocksService.selectDistributedLocksByDistributedLocksId(lockMarker);
        if (distributedLocksDOByQuery != null) {
            if (distributedLocksDOByQuery.getLockOwner().equals(lockOwner)) {
                // 锁已经被当前线程获取，做可重入处理，同时更新锁的过期时间, 如果更新成功，则代表再次获取锁成功。
                return tryReleaseForSelfLocked(lockMarker, lockOwner);
            }

            if ((distributedLocksDOByQuery.getUpdateAt().getTime() + distributedLocksDOByQuery.getLockExpiryTime()) < System.currentTimeMillis()) {
                //锁过期，释放锁。针对过期的锁，可以由任意线程进行释放。
                return tryReleaseForExpired(lockMarker);
            }
        }
        return false;
    }

    private boolean tryReentrantLock(@NotEmpty String lockMarker, @NotEmpty String lockOwner, @NotEmpty Integer lockExpireTime) {
        DistributedLocksDO distributedLocksDO = new DistributedLocksDO();
        distributedLocksDO.setDistributedLocksId(lockMarker);
        distributedLocksDO.setLockExpiryTime(lockExpireTime);
        distributedLocksDO.setLockOwner(lockOwner);
        return distributedLocksService.updateDistributedLocksByDistributedLocksId(distributedLocksDO) == 1;
    }

    private boolean tryReleaseForExpired(String lockMarker) {
        //锁过期，释放锁。针对过期的锁，可以由任意线程进行释放。
        DistributedReleaseLocksDO distributedReleaseLocksDO = new DistributedReleaseLocksDO();
        distributedReleaseLocksDO.setDistributedLocksId(lockMarker);
        // 虽然刚刚已经判断过锁已经过期，但是为了防止并发，即其他线程又重新获取了锁，故而在SQL语句中，追加乐观锁，删除锁同时判断一次锁是否过期。
        distributedReleaseLocksDO.setHasExpiredCheckWhenDeleteCondition(true);
        return distributedLocksSpecialDAO.deleteDistributedLocksByCondition(distributedReleaseLocksDO) == 1;
    }

    private boolean tryReleaseForSelfLocked(String lockMarker, String lockOwner) {
        //锁过期，释放锁。针对过期的锁，可以由任意线程进行释放。
        // 锁已经被当前线程获取，做可重入处理，同时更新锁的过期时间, 如果更新成功，则代表再次获取锁成功。
        DistributedReleaseLocksDO distributedReleaseLocksDO = new DistributedReleaseLocksDO();
        distributedReleaseLocksDO.setDistributedLocksId(lockMarker);
        distributedReleaseLocksDO.setLockOwner(lockOwner);
        return distributedLocksSpecialDAO.deleteDistributedLocksByCondition(distributedReleaseLocksDO) == 1;
    }
}
